#pragma once
#include <exception>
#include <iostream>
#include <fstream>
#include <string>
#include <list>
#include <io.h>
#include "ATA.h"
#include "PCI.h"
#include "OlsApiInitMem.h"

using namespace std;

typedef unsigned char byte;
typedef unsigned short ushort;
typedef unsigned long ulong;
typedef unsigned long long uint64;
typedef byte* bytearray;

#define MASTER 0x40
#define SLAVE  0x50

#define BIT0 0x01
#define BIT1 0x02
#define BIT2 0x04
#define BIT3 0x08
#define BIT4 0x10
#define BIT5 0x20
#define BIT6 0x40
#define BIT7 0x80

#define AMNF BIT0
#define TONF BIT1
#define ABRT BIT2
#define MCR  BIT3
#define IDNF BIT4
#define MC   BIT5
#define UNC  BIT6
#define BBK  BIT7

#define ERR  BIT0
#define INDX BIT1
#define CORR BIT2
#define DRQ  BIT3
#define DSC  BIT4
#define DF   BIT5
#define DRDY BIT6
#define BSY  BIT7

#define inp(port) ReadIoPortByte(port)
#define inpw(port) ReadIoPortWord(port)
#define inpd(port) ReadIoPortDword(port)
#define outp(port, value) WriteIoPortByte(port, value)
#define outpw(port, value) WriteIoPortWord(port, value)
#define outpd(port, value) WriteIoPortDword(port, value)

static struct ATAPort
{
	ushort base;
	ushort alt;
	byte   dev;
}ata_port;

static struct ATAReg
{
	union DATA { ushort address; ulong  value; }data; // Data register
	union FE   { ushort address; ushort value; }fe;   // 1 - Features  | Error register
	union SC   { ushort address; ushort value; }sc;   // 2 - Sector Count | Interrupt Reason register
	union LOW  { ushort address; ushort value; }low;  // 3 - LBA Low register
	union MID  { ushort address; ushort value; }mid;  // 4 - LBA Mid/Byte Count Low register
	union HIGH { ushort address; ushort value; }high; // 5 - LBA High/Byte Count High register
	union DEV  { ushort address; byte   value; }dev;  // 6 - Device register
	union CMD  { ushort address; byte   value; }cmd;  // 7 - Command | Status register
	union ALT  { ushort address; byte   value; }alt;  // Device Control | Alternate Status register
}ata_reg;

static HMEMORYMODULE h_OpenLibSys;
static list<ATAPort> ata_controller = {};
static ushort cur_error = 0xEE, cur_status = 0xEE;
static IDENTIFY_DEVICE_DATA ident_dev_data;

static bool init_drv();
static bool deinit_drv();
static void enum_ata_controller(list<ATAPort> ata_controller);
static void set_ata_controller();
static void device_select(byte device);
static void set_reg_addr(ushort base, ushort alt, byte dev = 0x40);
static void get_reg_value();
static bool reg_error(byte bit);
static bool reg_status(byte bit);
static bool eob(ulong timeout);
static bool rstat_bit(byte bit = 0);
static bool rstat_bitclr(byte bit = 0);
static bool check_drq();
static bool wait_not_busy(ulong timeout = 10);
static bool eto(ulong timeout);
static void write_lba(ulong ulba, ushort sc, byte cmd, ushort fe = 0);
static byte* lba_to_bytes(ulong ulba);
static ulong get_lba();
static ulong get_max_lba();
static bool set_max_lba(ulong ulba);
static int read_verify_lba(ulong ulba, ushort sc, ulong timeout = 10);
static void write_uncorrectable(ulong ulba, ushort sc);
static bool send_ata_cmd(ushort fe, ushort sc, ushort low, ushort mid, ushort high, byte dev, byte cmd, ulong timeout = 10);
static bool ata_cmd(ushort fe, ushort sc, ushort low, ushort mid, ushort high, byte cmd, ulong timeout = 10);
static bool ata_cmd(byte cmd, ulong timeout = 10);
static void soft_reset();
static bool cmd_identify();
static bool cmd_sleep_drive();
static bool cmd_standby();
static bool cmd_idle();
static bool cmd_wake_drive();
static bool cmd_recal();
static bool cmd_rdy();
static bool cmd_set_pwd();
static bool cmd_unlock_unit();
static bool cmd_erase_prep();
static bool cmd_erase_unit();
static bool cmd_freeze_lock();
static bool cmd_disable_pwd();
static bool cmd_smart_on();
static bool read_file(const char* filename, byte* buffer);
static bool save_file(const char* filename, byte* buffer);
static bool read_data(byte* buffer, ulong timeout = 10);
static bool write_data(byte* buffer, ulong timeout = 10);
static bool _read_data(byte* buffer, ulong timeout = 10);
static bool _write_data(byte* buffer, ulong timeout = 10);
static string get_identify();
static bool get_identify_data(byte* buffer);
static string get_char(byte* buffer, ulong offset, int size);
static string dec_identify(byte* buffer);
//-------------------------------------------------------------------------------
static bool vscon();
static bool vscoff();
static bool smart_read_log(ushort sc, ushort key);
static bool smart_write_log(ushort sc, ushort key);
static ushort get_sectors();
static bool smart_read_data(byte* buffer, ulong offset = 0, ushort count = 0);
static bool smart_write_data(byte* buffer, ulong offset = 0, ushort count = 0);

static bool init_drv()
{
	if (h_OpenLibSys)
		return true;
	return InitOpenLibSys(h_OpenLibSys);
}

static bool deinit_drv()
{
	if (h_OpenLibSys == NULL)
		return true;
	return DeinitOpenLibSys(h_OpenLibSys);
}

static void enum_ata_controller(list<ATAPort> ata_controller)
{
	byte bar = PCICFG_REG_BAR0;
	for (int bus = 0; bus < 256; bus++)
	{
		for (int dev = 0; dev < 32; dev++)
		{
			for (int func = 0; func < 8; func++)
			{
				ulong address = PciBusDevFunc(bus, dev, func);
				ulong vidValue = ReadPciConfigDword(address, 0x00);
				if ((vidValue == 0xFFFFFFFF) || (vidValue == 0))
					continue;
				ulong classValue = ReadPciConfigDword(address, 0x08);
				byte headerType = ReadPciConfigByte(address, 0x0E);
				if (((classValue >> 24) & 0xFF) != 1)
					continue;
				if (headerType != 0)
					continue;
				for (int i = 0; i < 3; i++)
				{
					ulong base = ReadPciConfigDword(address, bar + i * 8);
					ulong alt = ReadPciConfigDword(address, bar + i * 8 + 4);
					if ((base & 1) && (alt & 1) && (base > 0x170) && (alt < 0xFFFF))
					{
						ATAPort ata_port;
						ata_port.base = (ushort)(base - 1);
						ata_port.alt = ushort(alt + 1);
						ata_controller.push_back(ata_port);
					}
				}
			}
		}
	}
}

static void set_ata_controller()
{
	ata_controller.clear();
	enum_ata_controller(ata_controller);
}

static void device_select(byte device)
{
	ata_port.dev = device;
	ata_reg.dev.value = device;
}

static void set_reg_addr(ushort base, ushort alt, byte dev)
{
	ata_port.base = base;
	ata_port.alt = alt;
	device_select(dev);
	ata_reg.data.address = base;
	ata_reg.fe.address = base + 1;
	ata_reg.sc.address = base + 2;
	ata_reg.low.address = base + 3;
	ata_reg.mid.address = base + 4;
	ata_reg.high.address = base + 5;
	ata_reg.dev.address = base + 6;
	ata_reg.cmd.address = base + 7;
	ata_reg.alt.address = alt;
}

static void get_reg_value()
{
	ata_reg.fe.value = inp(ata_reg.fe.address);
	ata_reg.sc.value = inp(ata_reg.sc.address);
	ata_reg.low.value = inp(ata_reg.low.address);
	ata_reg.mid.value = inp(ata_reg.mid.address);
	ata_reg.high.value = inp(ata_reg.high.address);
	ata_reg.dev.value = inp(ata_reg.dev.address);
	ata_reg.cmd.value = inp(ata_reg.cmd.address);
	ata_reg.alt.value = inp(ata_reg.alt.address);
	cur_error = ata_reg.fe.value;
	cur_status = ata_reg.alt.value;
}

static bool reg_error(byte bit)
{
	return inp(ata_reg.fe.address) & bit;
}

static bool reg_status(byte bit)
{
	return inp(ata_reg.alt.address) & bit;
}

static bool eob(ulong timeout)
{
	return reg_status(ERR) || reg_status(BSY);
}

static bool rstat_bit(byte bit)
{
	byte reg_bit[8] = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80 };
	return inp(ata_reg.alt.address) & reg_bit[bit];
}

static bool rstat_bitclr(byte bit)
{
	byte reg_bit[8] = { 1, 2, 4, 8, 0x10, 0x20, 0x40, 0x80 };
	return (inp(ata_reg.alt.address) & reg_bit[bit]) != reg_bit[bit];
}

static bool check_drq()
{
	if (reg_status(DRQ))
		return true;
	bool result = false;
	ulong start = GetTickCount();
	while (true)
	{
		if (reg_status(DRQ))
		{
			result = true;
			break;
		}
		if (GetTickCount() - start > 3000)
			break;
	}
	return result;
}

static bool wait_not_busy(ulong timeout)
{
	bool result = true;
	byte stat = inp(ata_reg.alt.address);
	if (stat & BSY)
	{
		ulong start = GetTickCount();
		while (stat & BSY)
		{
			if ((GetTickCount() - start) > (timeout * 1000))
			{
				result = false;
				break;
			}
			stat = inp(ata_reg.alt.address);
		}
	}
	return result;
}

static bool eto(ulong timeout)
{
	return reg_status(ERR) || (wait_not_busy(timeout) == false);
}

static void write_lba(ulong ulba, ushort sc, byte cmd, ushort fe)
{
	byte dev = ata_reg.dev.value;
	byte lba[6] = { 0 };
	for (int i = 0; i < 6; i++)
	{
		lba[i] = (byte)(ulba >> 8);
		ulba >>= 8;
		outp(ata_reg.fe.address, fe >> 8);
		outp(ata_reg.sc.address, sc >> 8);
		outp(ata_reg.low.address, lba[3]);
		outp(ata_reg.mid.address, lba[4]);
		outp(ata_reg.high.address, lba[5]);
		outp(ata_reg.fe.address, fe & 0xFF);
		outp(ata_reg.sc.address, sc & 0xFF);
		outp(ata_reg.low.address, lba[0]);
		outp(ata_reg.mid.address, lba[1]);
		outp(ata_reg.high.address, lba[2]);
		outp(ata_reg.dev.address, dev);
		outp(ata_reg.cmd.address, cmd);
	}
}

static byte* lba_to_bytes(ulong ulba)
{
	byte lba[6] = { 0 };
	for (int i = 5; i >= 0; i--)
	{
		lba[i] = ulba & 0xFF;
		ulba >>= 8;
	}
	return lba;
}

static ulong get_lba()
{
	byte lba[6] = { 0 };
	lba[3] = inp(ata_reg.high.address);
	lba[4] = inp(ata_reg.mid.address);
	lba[5] = inp(ata_reg.low.address);
	outp(ata_reg.alt.address, 0x80);
	lba[0] = inp(ata_reg.high.address);
	lba[1] = inp(ata_reg.mid.address);
	lba[2] = inp(ata_reg.low.address);
	ulong ulba = 0;
	for (int i = 0; i < 5; i++)
	{
		ulba = ulba + lba[i];
		ulba <<= 8;
	}
	return ulba + lba[5];
}

static ulong get_max_lba()
{
	if (!ata_cmd(0x27))
		return 0;
	return get_lba();
}

static bool set_max_lba(ulong ulba)
{
	if (ulba > get_max_lba())
		return false;
	byte* lba = lba_to_bytes(ulba);
	ushort dev = ata_reg.dev.value;
	outp(ata_reg.low.address, lba[2]);
	outp(ata_reg.low.address, lba[5]);
	outp(ata_reg.mid.address, lba[1]);
	outp(ata_reg.mid.address, lba[4]);
	outp(ata_reg.high.address, lba[0]);
	outp(ata_reg.high.address, lba[3]);
	outp(ata_reg.dev.address, (byte)dev);
	outp(ata_reg.cmd.address, 0x37);
	return ulba == get_lba();
}

static int read_verify_lba(ulong ulba, ushort sc, ulong timeout)
{
	write_lba(ulba, sc, READ_VERIFY_SECTORS_EXT);
	ulong start = GetTickCount();
	byte stat = inp(ata_reg.alt.address);
	while (true)
	{
		while (stat & BSY)
		{
			if (GetTickCount() - start > (timeout * 1000))
			{
				cmd_recal();
				return -6;
			}
			stat = inp(ata_reg.alt.address);
		}
		if (stat & INDX)
		{
			cmd_recal();
			ulba -= 1;
			return -15;
		}
		if (stat == 0x10)
			return -16;
		if ((stat == 0x50) || (stat == 0x51))
			break;
	}
	byte erro = inp(ata_reg.fe.address);
	if (erro & AMNF)
	{
		cmd_recal();
		return -7;
	}
	int result = 0;
	if (stat & ERR)
	{
		result = (erro & TONF) ? -8 : -1;
		result = (erro & ABRT) ? -9 : -1;
		result = (erro & IDNF) ? -10 : -1;
		result = (erro & UNC) ? -11 : -1;
		result = (erro & BBK) ? -12 : -1;
		ulba = get_lba();
		cmd_recal();
	}
	else
	{
		result = GetTickCount() - start;
		ulba = ulba + sc - 1;
	}
	return result;
}

static void write_uncorrectable(ulong ulba, ushort sc)
{
	write_lba(ulba, sc, WRITE_UNCORRECTABLE_EXT, 0xA5);
}

static bool send_ata_cmd(ushort fe, ushort sc, ushort low, ushort mid, ushort high, byte dev, byte cmd, ulong timeout)
{
	outp(ata_reg.fe.address, fe >> 8);
	outp(ata_reg.sc.address, sc >> 8);
	outp(ata_reg.low.address, low >> 8);
	outp(ata_reg.mid.address, mid >> 8);
	outp(ata_reg.high.address, high >> 8);
	outp(ata_reg.fe.address, fe & 0xFF);
	outp(ata_reg.sc.address, sc & 0xFF);
	outp(ata_reg.low.address, low & 0xFF);
	outp(ata_reg.mid.address, mid & 0xFF);
	outp(ata_reg.high.address, high & 0xFF);
	outp(ata_reg.dev.address, dev);
	outp(ata_reg.cmd.address, cmd);
	if (!wait_not_busy(timeout))
		return false;
	return rstat_bitclr(0);
}

static bool ata_cmd(ushort fe, ushort sc, ushort low, ushort mid, ushort high, byte cmd, ulong timeout)
{
	byte dev = ata_reg.dev.value;
	return send_ata_cmd(fe, sc, low, mid, high, dev, cmd, timeout);
}

static bool ata_cmd(byte cmd, ulong timeout)
{
	byte dev = ata_reg.dev.value;
	return send_ata_cmd(0, 0, 0, 0, 0, dev, cmd, timeout);
}

static void soft_reset()
{
	outp(ata_reg.alt.address, 4);
	Sleep(1);
	outp(ata_reg.alt.address, 0);
}

static bool cmd_identify()
{
	return ata_cmd(0xEC) && reg_status(DRQ);
}

static bool cmd_sleep_drive()
{
	return ata_cmd(0xE6);
}

static bool cmd_standby()
{
	return ata_cmd(0xE0, 3);
}

static bool cmd_idle()
{
	return ata_cmd(0xE1, 30);
}

static bool cmd_wake_drive()
{
	soft_reset();
	outp(ata_reg.cmd.address, 0xE0);
	if (!wait_not_busy(3))
		return false;
	outp(ata_reg.cmd.address, 0xE1);
	return wait_not_busy(30);
}

static bool cmd_recal()
{
	return ata_cmd(0x10, 30);
}

static bool cmd_rdy()
{
	return send_ata_cmd(0, 0, 0, 0, 0, 0x10, 0);
}

static bool cmd_set_pwd()
{
	return ata_cmd(0xF1) && reg_status(DRQ);
}

static bool cmd_unlock_unit()
{
	return ata_cmd(0xF2) && reg_status(DRQ);
}

static bool cmd_erase_prep()
{
	return ata_cmd(0xF3);
}

static bool cmd_erase_unit()
{
	return ata_cmd(0xF4) && reg_status(DRQ);
}

static bool cmd_freeze_lock()
{
	return ata_cmd(0xF5);
}

static bool cmd_disable_pwd()
{
	return ata_cmd(0xF6) && reg_status(DRQ);
}

static bool cmd_smart_on()
{
	return ata_cmd(0xD8, 0, 0, 0x4F, 0xC2, 0xB0) && reg_status(DRDY);
}

static bool read_file(const char* filepath, byte* buffer)
{
	try
	{
		FILE* file;
		errno_t err = fopen_s(&file, filepath, "rb");
		if (err != 0)
			return false;
		long size = _filelength(_fileno(file)) / sizeof(byte);
		buffer = (byte*)calloc(size, sizeof(byte));
		fread(buffer, sizeof(byte), size, file);
		fclose(file);
	}
	catch (const std::exception&)
	{
		return false;
	}
	return true;
}

static bool save_file(const char* filepath, byte* buffer)
{
	try
	{
		FILE* file;
		errno_t err = fopen_s(&file, filepath, "wb");
		if (err != 0)
			return false;
		size_t count = _msize(buffer);
		fwrite(buffer, 1, count * 512, file);
		fclose(file);
	}
	catch (const std::exception&)
	{
		return false;
	}
	return true;
}

static bool read_data(byte* buffer, ulong timeout)
{
	ushort count = (ushort)(_msize(buffer) / 512);
	if (count == 0)
		return false;
	ulong offset = 0;
	byte stat = inp(ata_reg.alt.address);
	while ((stat & DRQ) || (stat & BSY) && (!(stat & ERR)))
	{
		if (stat & BSY)
		{
			if (offset == count * 512)
				break;
			ulong start = GetTickCount();
			while (stat & BSY)
			{
				if ((GetTickCount() - start) > (timeout * 1000))
					return false;
				stat = inp(ata_reg.alt.address);
			}
		}
		if (stat & DRQ)
		{
			try
			{
				ulong data = inpd(ata_reg.data.address);
				memcpy(&buffer[offset], &data, 4);
				offset += 4;
			}
			catch (std::exception)
			{
				return false;
			}
		}
		stat = inp(ata_reg.alt.address);
		if (stat & ERR)
			return false;
	}
	wait_not_busy(10);
	return offset == count * 512;
}

static bool write_data(byte* buffer, ulong timeout)
{
	ushort count = (ushort)(_msize(buffer) / 512);
	if (count == 0)
		return false;
	ulong offset = 0;
	byte stat = inp(ata_reg.alt.address);
	while ((stat & DRQ) || (stat & BSY) && (!(stat & ERR)))
	{
		if (stat & BSY)
		{
			if (offset == count * 512)
				break;
			ulong start = GetTickCount();
			while (stat & BSY)
			{
				if ((GetTickCount() - start) > (timeout * 1000))
					return false;
				stat = inp(ata_reg.alt.address);
			}
		}
		if (stat & DRQ)
		{
			try
			{
#ifdef _WIN64				
				ulong data = 0;
				memcpy(&data, &buffer[offset], 4);
				outpd(ata_reg.data.address, data);
				offset += 4;
#else
				ushort data = 0;
				memcpy(&data, &buffer[offset], 2);
				outpw(ata_reg.data.address, data);
				offset += 2;
#endif
			}
			catch (std::exception)
			{
				return false;
			}
		}
		stat = inp(ata_reg.alt.address);
		if (stat & ERR)
			return false;
	}
	return offset == count * 512;
}

static bool _read_data(byte* buffer, ulong timeout)
{
	ushort count = (ushort)(_msize(buffer) / 512);
	if (count == 0)
		return false;
	ulong offset = 0;
	byte stat = inp(ata_reg.alt.address);
	for (int i = 0; i < count; i++)
	{
		for (int j = 0; j < 512; j += 4)
		{
			if (stat & BSY)
			{
				ulong start = GetTickCount();
				while (stat & BSY)
				{
					if ((GetTickCount() - start) > (timeout * 1000))
						return false;
					stat = inp(ata_reg.alt.address);
				}
			}
			if (stat & DRQ)
			{
				try
				{
					ulong data = inpd(ata_reg.data.address);
					memcpy(&buffer[offset], &data, 4);
					offset += 4;
				}
				catch (std::exception)
				{
					return false;
				}
			}
			stat = inp(ata_reg.alt.address);
			if (stat & ERR)
				return false;
		}
	}
	wait_not_busy(10);
	return offset == count * 512;
}

static bool _write_data(byte* buffer, ulong timeout)
{
	ushort count = (ushort)(_msize(buffer) / 512);
	if (count == 0)
		return false;
	ulong offset = 0;
	byte stat = inp(ata_reg.alt.address);
	for (int i = 0; i < count; i++)
	{
#ifdef _WIN64
		for (int j = 0; j < 512; j += 4)
#else
		for (int j = 0; j < 512; j += 2)
#endif
		{
			if (stat & BSY)
			{
				ulong start = GetTickCount();
				while (stat & BSY)
				{
					if ((GetTickCount() - start) > (timeout * 1000))
						return false;
					stat = inp(ata_reg.alt.address);
				}
			}
			if (stat & DRQ)
			{
				try
				{
#ifdef _WIN64				
					ulong data = 0;
					memcpy(&data, &buffer[offset], 4);
					outpd(ata_reg.data.address, data);
					offset += 4;
#else
					ushort data = 0;
					memcpy(&data, &buffer[offset], 2);
					outpw(ata_reg.data.address, data);
					offset += 2;
#endif
				}
				catch (std::exception)
				{
					return false;
				}
			}
			stat = inp(ata_reg.alt.address);
			if (stat & ERR)
				return false;
		}
	}
	return offset == count * 512;
}

static string get_identify()
{
	byte* ident_buffer = new byte[512];
	string result = "";
	get_identify_data(ident_buffer);
	result = dec_identify(ident_buffer);
	delete[] ident_buffer;
	return result;
}

static bool get_identify_data(byte* buffer)
{
	if (!cmd_identify())
		return false;
	if (!read_data(buffer, 1))
		return false;
	memset(&ident_dev_data, 0, sizeof(ident_dev_data));
	memmove(&ident_dev_data, buffer, sizeof(buffer));
	return true;
}

static string get_char(byte* buffer, ulong offset, int size)
{
	string temp = "";
	for (int i = 0; i < size; i += 2)
	{
		temp += char(buffer[offset + i + 1]);
		temp += char(buffer[offset + i]);
	}
	return temp;
}

static string dec_identify(byte* buffer)
{
	string ident = "\nDrive Identify Data";
	ident += "\n------------------------------------------------------------";
	string model = get_char(buffer, 54, 40);
	string fwrev = get_char(buffer, 46, 8);
	string serial = get_char(buffer, 20, 20);
	ident += "\nModel Number   : " + model;
	ident += "\nFirmware Rev.  : " + fwrev;
	ident += "\nSerial Number  : " + serial;
	uint64 lba0 = ident_dev_data.Max48BitLBA[0];
	uint64 lba1 = ident_dev_data.Max48BitLBA[1];
	uint64 lba = lba0 + (lba1 << 32);
	ident += "\nUser Sectors   : " + to_string(lba) + " (";
	if (lba1 == 0)
		ident += to_string(int(lba * 512 / 1000000000)) + "G)";
	else
		ident += to_string(int(lba * 512 / 1000000)) + "T)";
	ushort uflag = buffer[82] + (buffer[83] << 8);
	if (uflag & 0x400)
		ident += " - HPA";
	ident += "\nBuffers        : " + to_string(int((buffer[42] + (buffer[43] << 8)) / 2)) + "KB";
	// Security Mode Feature Set
	uflag = buffer[164] + (buffer[165] << 8);
	string feats(4, '\0');
	sprintf_s(&feats[0], 4, "%04X", uflag);
	if (uflag & 2)
		//ident += "\nFeature Sets   : (0x%04X)security supported", uflag;
		ident += "\nFeature Sets   : (0x" + feats + ")security supported";
	else
		//ident += "\nFeature Sets   : (0x%04X)security unsupported", uflag;
		ident += "\nFeature Sets   : (0x" + feats + ")security unsupported";
	// Security Status
	uflag = buffer[256] + (buffer[257] << 8);
	if (uflag & 1)
	{
		string sest(4, '\0');
		sprintf_s(&sest[0], 4, "%04X", uflag);
		//ident += "\nSecurity Status: (0x%04X)security supported", uflag;
		ident += "\nSecurity Status: (0x" + sest + ")security supported";
		if (uflag & 2)
		{
			ident += ", enabled";
			if (uflag & 256)
				ident += ", level is maximum";
			else
				ident += ", level is high";
		}
		else if (uflag & 4)
			ident += ", locked";
		else if (uflag & 8)
			ident += ", frozen";
		else if (uflag & 0x10)
			ident += ", count has expired";
		// Time required for Security Erase Unit Completion
		if (uflag & 0x20)
		{
			ident += "\nEnhanced Erase : ";
			ushort value = buffer[180] + (buffer[181] << 8);
			if (value == 0)
				ident += "value not specified";
			else if (value == 255)
				ident += ">508 minutes";
			else
				ident += to_string(value * 2) + " minutes";
		}
		ident += "\nNormal Erase   : ";
		ushort value = buffer[178] + (buffer[179] << 8);
		if (value == 0)
			ident += "value not specified";
		else if (value == 255)
			ident += ">508 minutes";
		else
			ident += to_string(value * 2) + " minutes";
	}
	// Display Automatic Acoustic Managment
	ident += "\nCurrent AAM Lvl: ";
	uflag = buffer[166] + (buffer[167] << 8);
	if (uflag & 0x200)
	{
		uflag = buffer[188];
		ident += to_string(uflag) + " ";
		if (uflag == 0xFF)
			ident += "(Reserved)";
		else if (uflag == 0xFE)
			ident += "(Performance)";
		else if (uflag >= 0x81)
			ident += "(Intermediate)";
		else if (uflag == 0x80)
			ident += "(Quiet Mode)";
		else if (uflag >= 1)
			ident += "(Retired)";
		else
			ident += "(Reserved)";
		uflag = buffer[172] + (buffer[173] << 8);
		if ((uflag & 0x200) == 0)
			ident += ", AAM is disabled";
		else
			ident += ", AAM is enabled";
	}
	else
		ident += "AAM not supported";
	// Display UDMA Mode
	ident += "\nUdma Mode      : ";
	uflag = buffer[176];
	if (uflag == 0xFF)
	{
		ident += "Disabled";
	}
	else
	{
		for (int i = 0; i < 9; i++)
		{
			if (uflag & 1)
				ident += to_string(i) + " ";
			uflag >>= 1;
		}
	}
	// Display Cache State
	ident += "\nCache State    : ";
	uflag = buffer[170];
	if (uflag & 64)
		ident += "RCache On, ";
	else
		ident += "RCache Off, ";
	if (uflag & 32)
		ident += "WCache On";
	else
		ident += "WCache Off";
	// Display Smart State
	ident += "\nSmart Enabled  : ";
	if (uflag & 1)
		ident += "On";
	else
		ident += "Off";
	return ident;
}

//-------------------------------------------------------------------------------

static bool vscon()
{
	if (!ata_cmd(0x45, 0, 0, 0x44, 0x57, 0x80))
		return false;
	return rstat_bitclr(0);
}

static bool vscoff()
{
	if (!ata_cmd(0x44, 0, 0, 0x44, 0x57, 0x80))
		return false;
	return rstat_bitclr(0);
}

static bool smart_read_log(ushort sc, ushort key)
{
	if (!ata_cmd(0xD5, sc, key, 0x4F, 0xC2, 0xB0))
		return false;
	return check_drq();
}

static bool smart_write_log(ushort sc, ushort key)
{
	if (!ata_cmd(0xD6, sc, key, 0x4F, 0xC2, 0xB0))
		return false;
	return check_drq();
}

static ushort get_sectors()
{
	return inp(ata_reg.mid.address) + (inp(ata_reg.high.address) << 8);
}

static bool smart_read_data(byte* buffer, ulong offset, ushort count)
{
	if (count == 0)
		count = get_sectors();
	while (count > 0)
	{
		byte blk = (count > 256) ? 256 : count;
		if (!smart_read_log(blk, 0xBF))
			return false;
		byte stat = inp(ata_reg.alt.address);
		while ((stat & DRQ) || (stat & BSY) && (!(stat & ERR)))
		{
			if (stat & BSY)
			{
				ulong start = GetTickCount();
				while (stat & BSY)
				{
					if (GetTickCount() - start > 10000)
						return false;
					stat = inp(ata_reg.alt.address);
				}
			}
			if (stat & DRQ)
			{
				try
				{
					ulong data = inpd(ata_reg.data.address);
					memcpy(&buffer[offset], &data, 4);
					offset += 4;
				}
				catch (std::exception&)
				{
					return false;
				}
			}
			stat = inp(ata_reg.alt.address);
			if (stat & ERR)
				return false;
		}
		count -= blk;
	}
	return count == 0;
}

static bool smart_write_data(byte* buffer, ulong offset, ushort count)
{
	if (count == 0)
		count = get_sectors();
	while (count > 0)
	{
		byte blk = (count > 256) ? 256 : count;
		if (!smart_write_log(blk, 0xBF))
			return false;
		byte stat = inp(ata_reg.alt.address);
		while ((stat & DRQ) || (stat & BSY) && (!(stat & ERR)))
		{
			if (stat & BSY)
			{
				ulong start = GetTickCount();
				while (stat & BSY)
				{
					if (GetTickCount() - start > 10000)
						return false;
					stat = inp(ata_reg.alt.address);
				}
			}
			if (stat & DRQ)
			{
				try
				{
#ifdef _WIN64					
					ulong data = 0;
					memcpy(&data, &buffer[offset], 4);
					outpd(ata_reg.data.address, data);
					offset += 4;
#else
					ushort data = 0;
					memcpy(&data, &buffer[offset], 2);
					outpw(ata_reg.data.address, data);
					offset += 2;
#endif
				}
				catch (std::exception&)
				{
					return false;
				}
			}
			stat = inp(ata_reg.alt.address);
			if (stat & ERR)
				return false;
		}
		count -= blk;
	}
	return count == 0;
}
